//! nand2teris assembler
//!
//! translat the assembly code to binary code.

use anyhow::Result;
use inst::Instruction;
use label::LabelDictionary;
use std::{fs, path::PathBuf};

/// Read the assembly code from a file and translate it to binary code.
pub fn translate(assembly_file: &str) -> Result<String> {
    let codes = lines(assembly_file)?;
    let mut dict = LabelDictionary::new();
    let mut instructions = Vec::new();
    let mut address = 0;
    for code in codes {
        if code.starts_with('(') {
            dict.add(&code, address)?;
        } else {
            instructions.push(Instruction::new(&code)?);
            address += 1;
        }
    }
    // println!("{dict:#?}");
    let mut binary: Vec<String> = Vec::with_capacity(instructions.len());
    for instruction in instructions {
        binary.push(format!("{:016b}", instruction.to_binary(&mut dict)?));
    }
    let mut output_file = PathBuf::from(assembly_file);
    output_file.set_extension("hack");
    // The last line must have a newline
    if !binary.is_empty() {
        binary.push("".into());
    }
    fs::write(&output_file, binary.join("\n"))?;

    Ok(output_file.display().to_string())
}

// read the assembly code's all lines
fn lines(assembly_file: &str) -> Result<Vec<String>> {
    Ok(fs::read_to_string(assembly_file)?
        .lines()
        .filter_map(|line| {
            let line = line.trim();
            if !line.is_empty() && !line.starts_with("//") {
                Some(clean_line(line))
            } else {
                None
            }
        })
        .collect())
}
fn clean_line(line: &str) -> String {
    if line.contains("//") {
        line.split_once("//").unwrap().0.into()
    } else {
        line.into()
    }
}

mod inst;

/// label dictionary
mod label;

pub mod cli;
